using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;

/*
 The System.Net namespace provides a simple programming interface for many of the protocols used on networks today.
 There is one fundamental thing missing in the System.Net namespace and that's a class that can be used to ping servers. 
 It is, however, possible to write the class yourself, using a raw Socket and the ICMP protocol.
The Org.Mentalis.Network.Icmp class defines a method that can be used to ping a specific server. 
It can also be easily extended to support other ICMP messages.
 */
namespace __Able.Core.Utilities
{
	/// <summary>
	/// Implements the ICMP messaging service.
	/// </summary>
	/// <remarks>Currently, the implementation only supports the echo message (better known as 'ping').</remarks>
	public class Icmp {
		/// <summary>
		/// Initializes an instance of the Icmp class.
		/// </summary>
		/// <param name="host">The host that will be used to communicate with.</param>
		public Icmp(IPAddress host) {
			Host = host;
		}
		/// <summary>
		/// Generates the Echo message to send.
		/// </summary>
		/// <returns>An array of bytes that represents the ICMP echo message to send.</returns>
		protected byte[] GetEchoMessageBuffer() {
			EchoMessage message = new EchoMessage();
			message.Type = 8;	// ICMP echo
			message.Data = new Byte[32];
			for (int i = 0; i < 32; i++) {
				message.Data[i] = 32;	// Send spaces
			}
			message.CheckSum = message.GetChecksum();
			return message.GetObjectBytes();
		}
		/// <summary>
		/// Initiates an ICMP ping with a timeout of 1000 milliseconds.
		/// </summary>
		/// <exception cref="SocketException">There was an error while communicating with the remote server.</exception>
		/// <returns>A TimeSpan object that holds the time it takes for a packet to travel to the remote server and back. A value of TimeSpan.MaxValue indicates a timeout.</returns>
		/// <example>
		/// The following example will ping the server www.mentalis.org ten times and print the results in the Console.
		/// <c>
		/// <pre>
		/// Icmp icmp = new Icmp(Dns.Resolve("www.mentalis.org").AddressList[0]);
		/// for (int i = 0; i &lt; 10; i++) {
		/// 	Console.WriteLine(icmp.Ping().TotalMilliseconds);
		/// }
		/// </pre>
		/// </c>
		/// </example>
		public TimeSpan Ping() {
			return Ping(1000);
		}
		/// <summary>
		/// Initiates an ICMP ping.
		/// </summary>
		/// <param name="timeout">Specifies the timeout in milliseconds. If this value is set to Timeout.Infinite, the method will never time out.</param>
		/// <returns>A TimeSpan object that holds the time it takes for a packet to travel to the remote server and back. A value of TimeSpan.MaxValue indicates a timeout.</returns>
		public TimeSpan Ping(int timeout) {
			TimeSpan ret;
			EndPoint remoteEP = new IPEndPoint(Host, 0);
			ClientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Raw, ProtocolType.Icmp);
			// Get the ICMP message
			byte [] buffer = GetEchoMessageBuffer();
			// Set the timeout timer
			HasTimedOut = false;
			PingTimeOut = new Timer(new TimerCallback(PingTimedOut), null, timeout, Timeout.Infinite);
			// Get the current time
			StartTime = DateTime.Now;
			// Send the ICMP message and receive the reply
			try {
				if (ClientSocket.SendTo(buffer, remoteEP) <= 0)
					throw new SocketException();
				buffer = new byte[buffer.Length + 20];
				if (ClientSocket.ReceiveFrom(buffer, ref remoteEP) <= 0)
					throw new SocketException();
			} catch (SocketException e) {
				if (HasTimedOut)
					return TimeSpan.MaxValue;
				else
					throw e;
			} finally {
				// Close the socket
				ClientSocket.Close();
				ClientSocket = null;
				// destroy the timer
				PingTimeOut.Change(Timeout.Infinite, Timeout.Infinite);
				PingTimeOut.Dispose();
				PingTimeOut = null;
			}
			// Get the time
			ret = DateTime.Now.Subtract(StartTime);
			return ret;
		}
		/// <summary>
		/// Called when the ping method times out.
		/// </summary>
		/// <param name="state">The source of the event. This is an object containing application-specific information relevant to the methods invoked by this delegate, or a null reference (Nothing in Visual Basic).</param>
		protected void PingTimedOut(object state) {
			HasTimedOut = true;
			// Close the socket (this will result in a throw of a SocketException in the Ping method)
			if (ClientSocket != null)
				ClientSocket.Close();
		}
		/// <summary>
		/// Gets or sets the address of the remote host to communicate with.
		/// </summary>
		/// <value>An IPAddress instance that specifies the address of the remote host to communicate with.</value>
		/// <exception cref="ArgumentNullException">The specified value is null (Nothing in VB.NET).</exception>
		protected IPAddress Host {
			get {
				return m_Host;
			}
			set {
				if (value == null)
					throw new ArgumentNullException();
				m_Host = value;
			}
		}
		/// <summary>
		/// Gets or sets the Socket that is used to communicate with the remote server.
		/// </summary>
		/// <value>A Socket object that is used to communicate with the remote server.</value>
		protected Socket ClientSocket {
			get {
				return m_ClientSocket;
			}
			set {
				m_ClientSocket = value;
			}
		}
		/// <summary>
		/// Gets or sets the time when the ping began/begins.
		/// </summary>
		/// <value>A DateTime object that specifies when the ping began.</value>
		protected DateTime StartTime {
			get {
				return m_StartTime;
			}
			set {
				m_StartTime = value;
			}
		}
		/// <summary>
		/// Gets or sets the timer that triggers the PingTimedOut method.
		/// </summary>
		/// <value>A Timer object that triggers the PingTimedOut method.</value>
		protected Timer PingTimeOut {
			get {
				return m_PingTimeOut;
			}
			set {
				m_PingTimeOut = value;
			}
		}
		/// <summary>
		/// Gets or sets a value that indicates whether the ping has timed out or not.
		/// </summary>
		/// <value>A boolean value that indicates whether the ping has timed out or not.</value>
		protected bool HasTimedOut {
			get {
				return m_HasTimedOut;
			}
			set {
				m_HasTimedOut = value;
			}
		}
		/// <summary>
		/// Releases all the resources.
		/// </summary>
		~Icmp() {
			if (ClientSocket != null)
				ClientSocket.Close();
		}
		// Private variables
		/// <summary>Stores the value of the Host property.</summary>
		private IPAddress m_Host;
		/// <summary>Stores the value of the ClientSocket property.</summary>
		private Socket m_ClientSocket;
		/// <summary>Stores the value of the StartTime property.</summary>
		private DateTime m_StartTime;
		/// <summary>Stores the value of the PingTimeOut property.</summary>
		private Timer m_PingTimeOut;
		/// <summary>Stores the value of the HasTimedOut property.</summary>
		private bool m_HasTimedOut;
	}
	
	/// <summary>
	/// Defines a base ICMP message
	/// </summary>
	public abstract class IcmpMessage {
		/// <summary>
		/// Initializes a new IcmpMessage instance.
		/// </summary>
		public IcmpMessage() {}
		/// <summary>
		/// Gets or sets the type of the message.
		/// </summary>
		/// <value>A byte that specifies the type of the message.</value>
		public byte Type {
			get {
				return m_Type;
			}
			set {
				m_Type = value;
			}
		}
		/// <summary>
		/// Gets or sets the message code.
		/// </summary>
		/// <value>A byte that specifies the message code.</value>
		public byte Code {
			get {
				return m_Code;
			}
			set {
				m_Code = value;
			}
		}
		/// <summary>
		/// Gets or sets the chacksum for this message.
		/// </summary>
		/// <value>An unsigned short that holds the checksum of this message.</value>
		public ushort CheckSum {
			get {
				return m_CheckSum;
			}
			set {
				m_CheckSum = value;
			}
		}
		/// <summary>
		/// Serializes the object into an array of bytes.
		/// </summary>
		/// <returns>An array of bytes that represents the ICMP message.</returns>
		public virtual byte [] GetObjectBytes() {
			byte [] ret = new byte[4];
			Array.Copy(BitConverter.GetBytes(Type), 0, ret, 0, 1);
			Array.Copy(BitConverter.GetBytes(Code), 0, ret, 1, 1);
			Array.Copy(BitConverter.GetBytes(CheckSum), 0, ret, 2, 2);
			return ret;
		}
		/// <summary>
		/// Calculates the checksum of this message.
		/// </summary>
		/// <returns>An unsigned short that holds the checksum of this ICMP message.</returns>
		public ushort GetChecksum() {
			ulong sum = 0;
			byte [] bytes = GetObjectBytes();
			// Sum all the words together, adding the final byte if size is odd
			int i;
			for (i = 0; i < bytes.Length - 1; i += 2) {
				sum += BitConverter.ToUInt16(bytes, i);
			}
			if (i != bytes.Length)
				sum += bytes[i];
			// Do a little shuffling
			sum = (sum >> 16) + (sum & 0xFFFF);
			sum += (sum >> 16);
			return (ushort)(~sum);
		}
		// Private variables
		/// <summary>Holds the value of the Type property.</summary>
		private byte m_Type = 0;
		/// <summary>Holds the value of the Code property.</summary>
		private byte m_Code = 0;
		/// <summary>Holds the value of the CheckSum property.</summary>
		private ushort m_CheckSum = 0;
	}
	
	/// <summary>
	/// Defines an ICMP message with an ID and a sequence number.
	/// </summary>
	public class InformationMessage : IcmpMessage {
		/// <summary>
		/// Initializes a new InformationMessage instance.
		/// </summary>
		public InformationMessage() {}
		/// <summary>
		/// Gets or sets the identification number.
		/// </summary>
		/// <value>An unsigned short that holds the identification number of this message.</value>
		public ushort Identifier {
			get {
				return m_Identifier;
			}
			set {
				m_Identifier = value;
			}
		}
		/// <summary>
		/// Gets or sets the sequence number.
		/// </summary>
		/// <value>An unsigned short that holds the sequence number of this message.</value>
		public ushort SequenceNumber {
			get {
				return m_SequenceNumber;
			}
			set {
				m_SequenceNumber = value;
			}
		}
		/// <summary>
		/// Serializes the object into an array of bytes.
		/// </summary>
		/// <returns>An array of bytes that represents the ICMP message.</returns>
		public override byte [] GetObjectBytes() {
			byte [] ret = new byte[8];
			Array.Copy(base.GetObjectBytes(), 0, ret, 0, 4);
			Array.Copy(BitConverter.GetBytes(Identifier), 0, ret, 4, 2);
			Array.Copy(BitConverter.GetBytes(SequenceNumber), 0, ret, 6, 2);
			return ret;
		}
		// Private variables
		/// <summary>Holds the value of the Identifier property.</summary>
		private ushort m_Identifier = 0;
		/// <summary>Holds the value of the SequenceNumber property.</summary>
		private ushort m_SequenceNumber = 0;
	}
	
	/// <summary>
	/// Defines an echo ICMP message.
	/// </summary>
	public class EchoMessage : InformationMessage {
		/// <summary>
		/// Initializes a new EchoMessage instance.
		/// </summary>
		public EchoMessage() {}
		/// <summary>
		/// Gets or sets the data of this message.
		/// </summary>
		/// <value>An array of bytes that represents the data of this message.</value>
		public byte[] Data {
			get {
				return m_Data;
			}
			set {
				m_Data = value;
			}
		}
		/// <summary>
		/// Serializes the object into an array of bytes.
		/// </summary>
		/// <returns>An array of bytes that represents the ICMP message.</returns>
		public override byte [] GetObjectBytes() {
			int length = 8;
			if (Data != null)
				length += Data.Length;
			byte [] ret = new byte[length];
			Array.Copy(base.GetObjectBytes(), 0, ret, 0, 8);
			if (Data != null)
				Array.Copy(Data, 0, ret, 8, Data.Length);
			return ret;
		}
		// Private variables
		/// <summary>Holds the value of the Data property.</summary>
		private byte[] m_Data;
	}
}
